package com.demo.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class NIOChatServer extends AbstractChatServer<SocketChannel> {

	private ServerSocketChannel serverSocketChannel;

	private static volatile ConcurrentHashMap<SocketChannel, String> channelNameMap = new ConcurrentHashMap<>();
	private Selector selector;
	private final Log LOG = LogFactory.getLog(NIOChatServer.class);

	public NIOChatServer(int port) {
		super(port);
	}

	@Override
	public void bootStrap() throws IOException, InterruptedException {
		serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.socket().bind(new InetSocketAddress(port), backlog);
		serverSocketChannel.configureBlocking(false);
		showMsg("server start.");
		// 开启选择器
		selector = Selector.open();
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			int readyCount = selector.select();
			if (readyCount > 0) {
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				while (iterator.hasNext()) {
					handle(iterator.next(), selector);
					iterator.remove();
				}
			} else {
				Thread.sleep(1000);
			}
		}
	}

	private void handle(SelectionKey key, Selector selector) {
		if (key.isAcceptable()) {
			// 有新连接
			try {
				ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
				SocketChannel sc = serverSocketChannel.accept();
				LOG.debug("new Connection from " + sc.getRemoteAddress());
				sc.configureBlocking(false);
				sc.register(selector, SelectionKey.OP_READ, null);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else if (key.isReadable()) {
			// 有新消息
			SocketChannel readSocketChannel = (SocketChannel) key.channel();
			String nickName = "";
			try {
				ByteBuffer dst = ByteBuffer.allocate(1024);
				readSocketChannel.read(dst);
				String msg = new String(dst.array()).trim();
				LOG.debug("msg=" + msg);
				LOG.debug("new msg from " + readSocketChannel.getRemoteAddress());
				showMsg(msg);
				if ((nickName = channelNameMap.get(readSocketChannel)) != null) {
					sendToAll(nickName + ":" + msg);
				} else {
					// set nick Name
					if (chatterMap.get(msg) == null) {
						nickName = msg;
						chatterMap.put(msg, readSocketChannel);
						channelNameMap.put(readSocketChannel, nickName);
						sendLine(NICKNAMEW_VALID, readSocketChannel);
					} else {
						sendLine(NICKNAMEW_NOT_VALID, readSocketChannel);
					}
				}
			} catch (IOException e1) {
				e1.printStackTrace();
				try {
					chatterMap.remove(nickName);
					channelNameMap.remove(readSocketChannel);
					readSocketChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}

			}

		}
	}

	@Override
	protected void sendToAll(String msg) {
		chatterMap.forEach((addr, channel) -> {
			ByteBuffer src = ByteBuffer.allocate(1024);
			src.put((msg + "\n").getBytes(StandardCharsets.UTF_8));
			src.flip();
			try {
				channel.write(src);
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}

	private void sendLine(String msg, SocketChannel c) {
		if (c.isConnected()) {
			msg += "\n";
			ByteBuffer buff = ByteBuffer.allocate(msg.getBytes().length + 1);
			buff.put(msg.getBytes());
			buff.flip();
			try {
				c.write(buff);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	protected void sendToPrivate(String msg, String nickName) {
		sendLine(msg, chatterMap.get(nickName));
	}

}
